//
//  HTTPRequest.swift
//  ClockWidget
//
//  Created by TSYH on 2024/3/14.
//

import Alamofire

public class RequestSession {
    public enum RequestError: Error {
        case networkError(error: NSError)
        case systemError(error: NSError)
        case businessError(code: Int, message: String)
        case unknownError
        
        public var code: Int {
            switch self {
            case .networkError(let error):
                return error.code
            case .systemError(let error):
                return error.code
            case .businessError(let code, _):
                return code
            case .unknownError:
                return -1770
            }
        }
        
        public var message: String? {
            switch self {
            case .networkError(let error):
                return error.localizedDescription
            case .systemError(let error):
                return error.localizedDescription
            case .businessError(_, let message):
                return message
            case .unknownError:
                return nil
            }
        }
    }
    
    public enum RequestResponse {
        case modeledValue(value: Any)
        case modeledValues(values: Array<Any>)
        case responseError(error: RequestError)
        
        public var value: Any? {
            switch self {
            case .modeledValue(let value):
                return value
            case .modeledValues(let values):
                return values
            case .responseError:
                return nil
            }
        }
        
        public var error: RequestError? {
            switch self {
            case .modeledValue, .modeledValues:
                return nil
            case .responseError(let error):
                return error
            }
        }
    }
    
    public typealias RequestCompletionHandler = (RequestResponse) -> Void
    
    var sessionManager: Alamofire.Session
    var baseURL: String
    lazy var cacheSession = CacheSession()
    
    private lazy var queue = DispatchQueue(label: "queue.request.session")
    
    public init(apiHost: String) {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 20
        
        var defaultHTTPHeaders: [String: String] = [:]
        defaultHTTPHeaders["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
        defaultHTTPHeaders["Connection"] = "close"
        configuration.httpAdditionalHeaders = defaultHTTPHeaders
        
        sessionManager = Alamofire.Session(configuration: configuration)
        baseURL = apiHost
    }
}

public extension RequestSession {
    @discardableResult
    func GET(_ request: BaseRequest,
             completion: @escaping RequestCompletionHandler) -> DataRequest? {
        var parameters = Parameters()
        parameters.appendCommonParameters()
        parameters.append(method: request.method,
                          parameters: request.params ?? [:])
        
        // 测试请求 || 本地请求 || 接口缓存
        guard handlePreviousRequest(request, completion: { [weak self] data in
            if let data = data {
                self?.serialization(data: data, for: request,
                                    completion: completion)
            } else {
                let resp = RequestResponse.responseError(error: RequestError.businessError(code: ResponseCode.dataNil.rawValue, message: "Data is Nil"))
                DispatchQueue.main.async {
                    completion(resp)
                }
            }
        }) else {
            return nil
        }
        
        let url = baseURL.appending(request.method)
        let headers = HTTPHeaders(RequestParamaters.headers)
        let afReq = sessionManager.request(url,
                                           method: .get,
                                           parameters: parameters,
                                           headers: headers)
            .responseData(queue: queue) { [weak self] result in
                self?.handleResponse(result,
                                     for: request,
                                     completion: completion)
            }
        return afReq
    }
    
    @discardableResult
    func POST(_ request: BaseRequest,
              completion: @escaping RequestCompletionHandler) -> DataRequest? {
        var parameters = Parameters()
        parameters.appendCommonParameters()
        parameters.append(method: request.method,
                          parameters: request.params ?? [:])
        
        // 测试请求 || 本地请求 || 接口缓存
        guard handlePreviousRequest(request, completion: { [weak self] data in
            if let data = data {
                self?.serialization(data: data, for: request,
                                    completion: completion)
            } else {
                let resp = RequestResponse.responseError(error: RequestError.businessError(code: ResponseCode.dataNil.rawValue, message: "Data is Nil"))
                DispatchQueue.main.async {
                    completion(resp)
                }
            }
        }) else {
            return nil
        }
        
        let url = baseURL.appending(request.method)
        let headers = HTTPHeaders(RequestParamaters.headers)
        let req = sessionManager.request(url,
                                         method: .post,
                                         parameters: parameters,
                                         encoding: JSONEncoding.default,
                                         headers: headers)
            .responseData(queue: queue) { [weak self] result in
                self?.handleResponse(result,
                                     for: request,
                                     completion: completion)
            }
        return req
    }
    
    func Upload(_ request: BaseRequest,
                data: Data,
                completion: @escaping RequestCompletionHandler) {
        let completionHandler: ((RequestResponse) -> Void) = { resp in
            DispatchQueue.main.async {
                completion(resp)
            }
        }
        
        let url = baseURL.appending(request.method)
        let header = HTTPHeaders(["Content-Type": "multipart/form-data"])
        sessionManager.upload(multipartFormData: { multipart in
            multipart.append(data, withName: "file",
                             fileName: "file.png",
                             mimeType: "image/png")
        }, to: url, method: .post, headers: header)
        .uploadProgress(queue: .main) { _ in
            
        }.responseData(queue: queue) { [weak self] result in
            self?.handleResponse(result,
                                 for: request,
                                 completion: completion)
        }
    }
}

extension RequestSession {
    func handlePreviousRequest(_ request: BaseRequest,
                               completion: @escaping (Data?) -> Void) -> Bool {
#if DEBUG
        // 测试数据
        if let mockFile = request.mockFile {
            cacheSession.requestFileData(with: mockFile,
                                         completion: completion)
            return false
        }
#endif
        
        // 本地数据
        if let localFile = request.localFile {
            cacheSession.requestFileData(with: localFile,
                                         completion: completion)
            return false
        }
        
        // 先读缓存返回
        if request.shouldReadCache,
           FileManager.default.fileExists(with: request.dataCacheKey) {
            cacheSession.fetchCache(for: request,
                                    completionHandler: completion)
            return true
        }
        
        return true
    }
}

extension RequestSession {
    /// 请求结果处理
    func handleResponse(_ response: AFDataResponse<Data>,
                        for request: BaseRequest,
                        completion: @escaping RequestCompletionHandler) {
        let completionHandler: ((RequestResponse) -> Void) = { resp in
            DispatchQueue.main.async {
                completion(resp)
            }
        }
        if let data = response.data {
            serialization(data: data, for: request,
                          completion: completion)
            cacheSession.saveCacheIfNeed(responseData: data,
                                         for: request)
        } else if let error = response.error as? CustomNSError {
            let nsError = NSError(domain: type(of: error).errorDomain,
                                  code: error.errorCode, userInfo: error.errorUserInfo)
            let response = RequestResponse.responseError(error: RequestError.networkError(error: nsError))
            completionHandler(response)
        } else if let error = response.error as NSError? {
            let response = RequestResponse.responseError(error: RequestError.networkError(error: error))
            completionHandler(response)
        } else {
            let response = RequestResponse.responseError(error: RequestError.unknownError)
            completionHandler(response)
        }
    }
    
    func serialization(data: Data,
                       for request: BaseRequest,
                       completion: @escaping RequestCompletionHandler) {
        let completionHandler: ((RequestResponse) -> Void) = { resp in
            DispatchQueue.main.async {
                completion(resp)
            }
        }
        do {
            let model = try request.model(from: data)
            let response = RequestResponse.modeledValue(value: model)
            completionHandler(response)
        } catch RequestError.businessError(let code, let message) {
            let response = RequestResponse.responseError(error: RequestError.businessError(code: code, message: message))
            completionHandler(response)
        } catch let error as NSError {
            let response = RequestResponse.responseError(error: RequestError.systemError(error: error))
            completionHandler(response)
        } catch {
            let response = RequestResponse.responseError(error: RequestError.unknownError)
            completionHandler(response)
        }
    }
}

extension RequestSession {
    
    public static func jsonObjectByRemovingKeysWithNullValues(_ JSONObject: Any) -> Any {
        switch JSONObject {
        case let JSONObject as [Any]:
            return JSONObject.map { jsonObjectByRemovingKeysWithNullValues($0) }
        case let JSONObject as [String: Any]:
            var mutableDictionary = JSONObject
            for (key, value) in JSONObject {
                switch value {
                case _ as NSNull:
                    mutableDictionary.removeValue(forKey: key)
                default:
                    mutableDictionary[key] = jsonObjectByRemovingKeysWithNullValues(value)
                }
            }
            return mutableDictionary
        default:
            return JSONObject
        }
    }
}
